import * as THREE from '../../../../modules/three/three.module.js'
import {flatten} from "../util/StaticMethods.js";
import {assets3dBaseUrl, mapColorCssToCode} from "../CommonConsts.js";
import RoutePointVO, {RoutePointActions} from "../../model/RoutePointVO.js";
import RouteVO from "../../model/RouteVO.js";

const _inverseMatrix = /*@__PURE__*/ new THREE.Matrix4();
const _ray = /*@__PURE__*/ new THREE.Ray();
const _sphere = /*@__PURE__*/ new THREE.Sphere();
const _position = /*@__PURE__*/ new THREE.Vector3();
let ndcRadius = 0.1

const spCircle = new THREE.TextureLoader().load( assets3dBaseUrl + 'texture/circle.png' );

// 负责渲染 AI 规划出来的航线
export default class SprayPathIndicator extends THREE.Points {
  /**
   * 数据来自于 AI 的航线规划
   * @type {SprayPath}
   */
  path
  w
  h

  lineMtl = new THREE.LineBasicMaterial({
    color: '#ffffff',
    toneMapped: false,
    transparent: true,
  })
  lineNoSprayMtl = new THREE.LineBasicMaterial({
    color: '#000',
    toneMapped: false,
    transparent: true,
  });

  pointMtl = new THREE.PointsMaterial( {
    size: 16,
    color: "#024611",
    sizeAttenuation: false,
    map: spCircle,
    alphaTest: 0.5,
    toneMapped: false,
    transparent: true } )

  arrowHelper

  constructor(sprayPath, w, h) {
    super()
    this.material = this.pointMtl
    this.path = sprayPath
    this.w = w
    this.h = h
    this._drawPath()
  }

  _drawPath() {
    this.geometry.setAttribute('position', new THREE.BufferAttribute(flatten(this.path.dronePath), 3))

    const [sprayCoords, noSprayCoords] = this.path.retrieveSprayAndUnSpraySegments();
    let lineGeo = new THREE.BufferGeometry();
    let line3d;
    if (sprayCoords.length > 1) {
      lineGeo.setAttribute('position', new THREE.BufferAttribute(flatten(sprayCoords), 3));
      line3d = new THREE.LineSegments(lineGeo, this.lineMtl);
      this.add(line3d);
    }

    if (noSprayCoords.length > 1) {
      lineGeo = new THREE.BufferGeometry();
      lineGeo.setAttribute('position', new THREE.BufferAttribute(flatten(noSprayCoords), 3));
      line3d = new THREE.LineSegments(lineGeo, this.lineNoSprayMtl);
      this.add(line3d);
    }

    const dir = this.path.facing.clone().negate()
    const arrowHelper = new THREE.ArrowHelper(dir, this.path.dronePath[0], 1, '#8a0082')
    arrowHelper.setLength(0.3, 0.3 * 0.4, 0.2)
    const arrowHelperEnd = new THREE.ArrowHelper(dir, this.path.dronePath[this.path.dronePath.length - 1], 1, '#009d32')
    arrowHelperEnd.setLength(0.3, 0.3 * 0.4, 0.2)
    this.add(arrowHelper, arrowHelperEnd)
    this.arrowHelper = arrowHelper
    arrowHelper.cone.geometry.computeBoundingBox()
    arrowHelper.cone.geometry.computeBoundingSphere()
    arrowHelper.cone.name = 'path arrow'
  }

  /**
   * 支持把用于展示的区域航线 转换为由手动航点组成的航线
   * @return {RouteVO}
   */
  switchToManualRoute() {
    const routePts = []
    const facing = this.path.facing
    const colorCode = mapColorCssToCode.get(this.path.cssColorStr)
    const sprayFlags = this.path._sprayFlags
    this.path.wallPath.forEach((pt, index) => {
      const routePoint = new RoutePointVO()
      routePoint.position.copy(pt)
      routePoint.normal.copy(facing)
      routePoint.offset = this.path.distanceToWall
      routePoint.action = sprayFlags[index] ? RoutePointActions.StartSpray : RoutePointActions.EndSpray
      routePts.push(routePoint)
    })
    const routeVO = new RouteVO()
    routeVO.colorCode = colorCode
    routeVO.sprayDistance = this.path.distanceToWall
    routeVO.pointsByJson = routePts
    routeVO.id = routeVO.routeOrder = this.path.workAreaIds[0]
    return routeVO
  }

  // 覆盖 THREE.Points 的raycast
  raycast( raycaster, intersects ) {
    const geometry = this.geometry;
    const matrixWorld = this.matrixWorld;
    const threshold = raycaster.params.Points.threshold;
    const drawRange = geometry.drawRange;

    // Checking boundingSphere distance to ray

    if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

    _sphere.copy( geometry.boundingSphere );
    _sphere.applyMatrix4( matrixWorld );
    _sphere.radius += threshold;

    if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;

    //

    _inverseMatrix.copy( matrixWorld ).invert();
    _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
    const hs = this.material.size * 0.5
    // 取画布长或宽
    const canvasSize = window.innerHeight > window.innerWidth ? window.innerWidth : window.innerHeight
    ndcRadius = hs / canvasSize

    const attributes = geometry.attributes;
    const positionAttribute = attributes.position;

    const start = Math.max( 0, drawRange.start );
    const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );

    for ( let i = start, l = end; i < l; i ++ ) {

      _position.fromBufferAttribute( positionAttribute, i );

      testPoint( _position, i, matrixWorld, raycaster, intersects, this );

    }
  }
}

const tmp1 = new THREE.Vector3()
const tmp2 = new THREE.Vector3()

/**
 * 世界尺度: 相机位置 到 相机聚焦点的距离
 * 官方 Points 的射线撞击方法是不精确的，撞击精确度依赖 raycaster.params.Points.threshold，这个值大致上由世界尺度决定，并且非常难用
 * 官方不提供精确撞击方法是因为无法提供，因为Points 中单个粒子的形状可以是任意的，可以是圆形，圆环，方块，三角形，多边形，雪花等
 * 以下撞击测试方法只针对粒子是圆形的情况，思路：
 * 1. 找射线最近点，这个点位于射线上，这个点 到 被测试点 距离是最短的，射线上其他点到 被测试点的距离都比这个长
 * 2. 射线最近点 和 被测试点，都通过 project 方法投影到相机的ndc空间（别名 标准设备空间， 是一个x、y和z值在-1.0到1.0的一小段空间），计算两个点在ndc空间中的距离
 * 3. this.material.size 和画布的大小 共同决定 Points的单个粒子在屏幕上绘制多大，把这个大小数值标量，转换到 ndc空间；
 * 和上一步算出的距离比较，上一步算出的距离比较小，则射线撞击到这个粒子
 * @param {THREE.Vector3} point
 * @param {number} index
 * @param {THREE.Matrix4} matrixWorld
 * @param {THREE.Raycaster} raycaster
 * @param intersects
 * @param {SprayPathIndicator} object
 */
function testPoint( point, index, matrixWorld, raycaster, intersects, object ) {
  const p1 = _ray.closestPointToPoint(point, tmp1).project(raycaster.camera)
  const p0 = tmp2.copy(point).project(raycaster.camera)
  const ndcDistance = p1.distanceTo(p0)

  if ( ndcDistance < ndcRadius ) {
    const intersectPoint = new THREE.Vector3();

    _ray.closestPointToPoint( point, intersectPoint );
    intersectPoint.applyMatrix4( matrixWorld );

    const distance = raycaster.ray.origin.distanceTo( intersectPoint );

    if ( distance < raycaster.near || distance > raycaster.far ) return;

    intersects.push( {

      distance: distance,
      distanceToRay: _ray.distanceToPoint(point),
      point: intersectPoint,
      index: index,
      face: null,
      object: object

    } );

  }
}
